from scipy.spatial import distance
import numpy as np
import matplotlib.pyplot as plt


class TWVRPAssignmentOld:
    def __init__(self, job_list, transport_cost_matrix, route_sequence, company):
        # 任务信息
        self._job_list = job_list
        # 交通时间信息
        self._transport_cost_matrix = transport_cost_matrix
        # 工作安排顺序信息
        self._route_sequence = route_sequence
        # 任务实体类
        self._job_entity_list = []
        # 起点信息
        self._company = company

    # 安排一个序列的工作
    def routeSequenceAssign(self):
        for index, value in enumerate(self._route_sequence):
            this_job_info = self._job_list[value]
            prev_job_info = None
            if index != 0:
                prev_job_info = self._job_entity_list[index - 1]
            jobEntity = JobEntity(this_job_info, prev_job_info, self._transport_cost_matrix)
            jobEntity.updateJobStatus()
            self._job_entity_list.append(jobEntity)

    # 画甘特图
    def drawGantt(self):
        MColor = ['red', 'blue', 'yellow', 'orange', 'green', 'moccasin', 'purple',
                  'pink', 'navajowhite', 'Thistle', 'Magenta', 'SlateBlue', 'RoyalBlue',
                  'Aqua', 'floralwhite', 'ghostwhite', 'goldenrod', 'mediumslateblue',
                  'navajowhite', 'navy', 'sandybrown']
        fig = plt.figure(figsize=(15, 6), dpi=70)
        # 横众轴显示字体为‘仿宋’的中文标签
        plt.rcParams['font.sans-serif'] = ['FangSong']
        plt.rcParams['axes.unicode_minus'] = False
        for index, each_job in enumerate(self._job_entity_list):
            job_id = each_job._this_job_info['job_id']
            job_name = each_job._this_job_info['job_name']
            prev_job_name = '公司'
            # 如果前序任务为空则是由公司出发
            if each_job._prev_job_info == None:
                prev_job_name = '公司'
            else:
                prev_job_name = each_job._prev_job_info._this_job_info['job_name']
            # 计算交通开销
            transport_cost = each_job._prev_to_now_work_trans_end_time \
                             - each_job._prev_to_now_work_trans_start_time
            # 画交通时间开销
            plt.barh(job_name, width=transport_cost,
                     height=0.8,
                     color='white',
                     edgecolor='black',
                     left=each_job._prev_to_now_work_trans_start_time)
            # 画工作开销
            plt.barh(job_name, width=each_job._this_job_info['work_cost'],
                     height=0.8,
                     color=MColor[job_id],
                     edgecolor='black',
                     left=each_job._now_work_start_time)
            # 交通开销表标明文字
            plt.text(x=each_job._prev_to_now_work_trans_start_time + (transport_cost / 2 - 0.25),
                     y=index - 0.2,
                     color='black',
                     weight='bold',
                     s=prev_job_name + '\n' + job_name,
                     size=8, fontproperties='SimSun')
            # 工作开销标明文字
            plt.text(x=each_job._now_work_start_time + (each_job._this_job_info['work_cost'] / 2 - 0.25),
                     y=index - 0.2,
                     color='black',
                     weight='bold',
                     s=job_name,
                     size=12, fontproperties='SimSun')

        plt.ylabel("维修任务", size=20, fontproperties='SimSun')
        plt.xlabel("工作小时", size=20, fontproperties='SimSun')
        plt.tick_params(labelsize=20)
        plt.tick_params(direction='in')
        total_max_time = self.getSequenceEndTime()
        plt.title('通线工时:' + str(total_max_time))
        plt.show()

    # 画甘特图
    def drawGanttOld(self):
        MColor = ['red', 'blue', 'yellow', 'orange', 'green', 'moccasin', 'purple',
                  'pink', 'navajowhite', 'Thistle', 'Magenta', 'SlateBlue', 'RoyalBlue',
                  'Aqua', 'floralwhite', 'ghostwhite', 'goldenrod', 'mediumslateblue',
                  'navajowhite', 'navy', 'sandybrown']
        fig = plt.figure(figsize=(15, 6), dpi=70)
        # 横众轴显示字体为‘仿宋’的中文标签
        plt.rcParams['font.sans-serif'] = ['FangSong']
        plt.rcParams['axes.unicode_minus'] = False
        for index, each_job in enumerate(self._job_entity_list):
            job_id = each_job._this_job_info['job_id']
            job_name = each_job._this_job_info['job_name']
            prev_job_name = '公司'
            if each_job._prev_job_info != None:
                prev_job_name = each_job._prev_job_info._this_job_info['job_name']
            else:
                prev_job_name = '公司'
            # 计算交通开销
            transport_cost = each_job._prev_to_now_work_trans_end_time - each_job._prev_to_now_work_trans_start_time
            # 画交通时间开销
            plt.barh(job_name, width=transport_cost,
                     height=0.8,
                     color='white',
                     edgecolor='black',
                     left=each_job._prev_to_now_work_trans_start_time)
            # 画工作开销
            plt.barh(job_name, width=each_job._this_job_info['work_cost'],
                     height=0.8,
                     color=MColor[job_id],
                     edgecolor='black',
                     left=each_job._now_work_start_time)
            # 交通开销表标明文字
            plt.text(x=each_job._prev_to_now_work_trans_start_time + (transport_cost / 2 - 0.25),
                     y=index - 0.2,
                     color='black',
                     weight='bold',
                     s=prev_job_name + '\n' + job_name,
                     size=8, fontproperties='SimSun')
            # 工作开销标明文字
            plt.text(x=each_job._now_work_start_time + (each_job._this_job_info['work_cost'] / 2 - 0.25),
                     y=index - 0.2,
                     color='black',
                     weight='bold',
                     s=job_name,
                     size=12, fontproperties='SimSun')

        plt.ylabel("维修任务", size=20, fontproperties='SimSun')
        plt.xlabel("工作小时", size=20, fontproperties='SimSun')
        plt.tick_params(labelsize=20)
        plt.tick_params(direction='in')
        total_max_time = self.getSequenceEndTime()
        plt.title('通线工时:' + str(total_max_time))
        plt.show()

    # 画位置图
    def drawPlot(self):
        plot_x_set = []
        plot_y_set = []
        plt.rcParams['font.sans-serif'] = ['FangSong']
        plt.rcParams['axes.unicode_minus'] = False
        # 画出点的位置
        # 先画起点
        plot_x_set.append(self._company['lon'])
        plot_y_set.append(self._company['lat'])
        plt.annotate(self._company['company_name'], (self._company['lon'], self._company['lat']))
        for index, value in enumerate(self._route_sequence):
            x = self._job_list[value]['lon']
            y = self._job_list[value]['lat']
            label = '顺序' + str(index + 1) + ':' + self._job_list[value]['job_location']
            plot_x_set.append(x)
            plot_y_set.append(y)
            plt.annotate(label, (x, y))
        plt.plot(plot_x_set, plot_y_set, 'b')
        plt.scatter(plot_x_set, plot_y_set, c='r')
        total_distance = self.getTotalDistance()
        plt.title('总距离' + str(total_distance))
        plt.show()

    # 获取通线时间(即全部任务完成的时间)
    def getSequenceEndTime(self):
        return self._job_entity_list[-1]._now_work_end_time

    # 获取通线距离(即全部任务完成的距离)
    def getTotalDistance(self):
        total_distance = 0.0
        for index in range(0, len(self._route_sequence)):
            this_value = self._route_sequence[index]
            this_x = self._job_list[this_value]['lon']
            this_y = self._job_list[this_value]['lat']
            if index == 0:
                prev_x = self._company['lon']
                prev_y = self._company['lat']
            else:
                prev_value = self._route_sequence[index - 1]
                prev_x = self._job_list[prev_value]['lon']
                prev_y = self._job_list[prev_value]['lat']
            mdistance = distance.cdist([[this_x, this_y]], [[prev_x, prev_y]], 'euclidean')
            total_distance = total_distance + mdistance
        return total_distance[0][0]


# 工作安排里面的人员实体类，保存人员的工作信息
class JobEntity:
    def __init__(self, this_job_info, prev_job_info, transport_cost_matrix):
        # 当前任务信息
        self._this_job_info = this_job_info
        # 前序任务信息
        self._prev_job_info = prev_job_info
        # 距离矩阵
        self._transport_cost_matrix = transport_cost_matrix
        # 当前任务开始时间
        self._now_work_start_time = 0
        # 当前任务结束时间
        self._now_work_end_time = 0
        # 前序任务到当前任务的运输开始时间
        self._prev_to_now_work_trans_start_time = 0
        # 前序任务到当前任务的运输结束时间，其实就是_now_work_start_time
        self._prev_to_now_work_trans_end_time = 0

    # 更新工作的状态
    def updateJobStatus(self):
        # 当前任务id和工作耗时,this_job_can_start_time为预约开始时间
        tihs_job_id = self._this_job_info['job_id']
        this_job_work_cost = self._this_job_info['work_cost']
        this_job_can_start_time = 0
        # 如果是2类任务就是要按照约定时间来，如果不是则可开始时间为0，即随时都可以，取决于上次任务结束时间
        if self._this_job_info['job_class'] == 2:
            this_job_can_start_time = self._this_job_info['start_time']
        else:
            this_job_can_start_time = 0
        # 先查出前置任务的id和时间，如果是-1则表示没有前置任务
        prev_job_id = -1
        prev_job_end_time = 0
        # 如果没有前序任务就是从公司出发
        if self._prev_job_info == None:
            prev_job_id = -1
            prev_job_end_time = 0
            self._prev_to_now_work_trans_start_time = prev_job_end_time
            self._prev_to_now_work_trans_end_time = self._prev_to_now_work_trans_start_time + \
                                                    self._transport_cost_matrix[-1][tihs_job_id]
            self._now_work_start_time = max(self._prev_to_now_work_trans_end_time, this_job_can_start_time)
            self._now_work_end_time = self._now_work_start_time + this_job_work_cost
        # 如果有前序任务就是从前序任务出发
        else:
            # 获取上一个任务的id
            prev_job_id = self._prev_job_info._this_job_info['job_id']
            # 获取上一个任务的结束时间
            prev_job_end_time = self._prev_job_info._now_work_end_time
            # 获取转移开始时间
            self._prev_to_now_work_trans_start_time = prev_job_end_time
            self._prev_to_now_work_trans_end_time = self._prev_to_now_work_trans_start_time + \
                                                    self._transport_cost_matrix[prev_job_id][tihs_job_id]
            self._now_work_start_time = max(self._prev_to_now_work_trans_end_time, this_job_can_start_time)
            self._now_work_end_time = self._now_work_start_time + this_job_work_cost


def create_data():
    # 任务信息,wokr_cost时间开销，单位小时；job_class任务类别，
    # 1为优先任务，2为时间窗任务
    job_list = [
        {'job_id': 0, 'job_name': '1号任务',
         'job_location': 'A地', 'lon': 5, "lat": 6,
         'work_cost': 2.5, 'job_class': 2, 'start_time': 8,
         'end_time': 16},
        {'job_id': 1, 'job_name': '2号任务',
         'job_location': 'B地', 'lon': 7, "lat": 7,
         'work_cost': 2.5, 'job_class': 2, 'start_time': 8,
         'end_time': 16},
        {'job_id': 2, 'job_name': '3号任务',
         'job_location': 'C地', 'lon': 2, "lat": 2,
         'work_cost': 2.5, 'job_class': 2, 'start_time': 16,
         'end_time': 24},
        {'job_id': 3, 'job_name': '4号任务',
         'job_location': 'D地', 'lon': 1, "lat": 4,
         'work_cost': 2.5, 'job_class': 2, 'start_time': 24,
         'end_time': 32},
        {'job_id': 4, 'job_name': '5号任务',
         'job_location': 'E地', 'lon': 8, "lat": 2,
         'work_cost': 2.5, 'job_class': 2, 'start_time': 24,
         'end_time': 32},
        {'job_id': 5, 'job_name': '6号任务',
         'job_location': 'F地', 'lon': 7, "lat": 2,
         'work_cost': 1, 'job_class': 1, 'start_time': None,
         'end_time': None},
        {'job_id': 6, 'job_name': '7号任务',
         'job_location': 'G地', 'lon': 4, "lat": 2,
         'work_cost': 1.5, 'job_class': 1, 'start_time': None,
         'end_time': None},
    ]
    company = {
        'company_name': '公司', 'lon': 4, "lat": 4
    }
    # 固定生成8*8的交通开销矩阵，默认第一行第二列为为第一个地点到第二个地点的时间开销，
    # 最后一行为公司到各个地点的开销，这样保证序号都是对的上，每行最后一列表示该点到公司的开销
    transport_cost_matrix = []
    points_list = []
    for index, value in enumerate(job_list):
        points_list.append((value['lon'], value['lat']))
    # 最后还要加上公司
    points_list.append((company['lon'], company['lat']))
    transport_cost_matrix = distance.cdist(points_list, points_list, 'euclidean')
    # 由于数字太大，全部除以2,且保留一位小数，比如2.2表示需要2.2小时即2小时12分
    transport_cost_matrix = np.around(transport_cost_matrix / 2, decimals=1)
    return job_list, transport_cost_matrix, company


if __name__ == '__main__':
    job_list, transport_cost_matrix, company = create_data()
    route_sequence = [6, 5, 2, 1, 3, 0, 4]
    twvrpAssignmentOld = TWVRPAssignmentOld(job_list, transport_cost_matrix, route_sequence, company)
    twvrpAssignmentOld.routeSequenceAssign()
    twvrpAssignmentOld.drawGantt()
    twvrpAssignmentOld.drawPlot()
